/******************************************************************************
 *      Mestrado em Ciência da Computação - UFMS
 *		Redes de Computadores - Prof. Dr. Ronaldo Alves Ferreira
 *		Alunos: Geraldo Landre, Marcel Tolentino, Vinícius Bueno
 *****************************************************************************/
#include "thread.h"
#include "tcb.h"

/**
 * Cria uma nova thread, com buffer para mensagens atribuido.
 * Insere nova thread no array de threads
 * @param bufflength
 * @return id da thread
 */
int newthread() {
    return _newthread(TCPQLEN);
}
int _newthread(u_short bufflength) {
    sem_wait(&thread_buff_mutex);

    int index = sync_dequeue(free_threads);                      // retira da fila de livres
    thread_buff[index]->buffer = newport_network(0, bufflength);       // aloca um buffer de msgs (porta aleatora)
    thread_buff[index]->thread = malloc(sizeof(pthread_t));
    thread_buff[index]->state = THREAD_NEW;                 // altera o estado para new

    sem_init(&(thread_buff[index]->suspend_mutex), 0, 1);

    sem_post(&thread_buff_mutex);
    return index;
}

int newthread_kernel() {
    return _newthread_kernel(TCPQLEN);
}
int _newthread_kernel(u_short bufflength) {
    sem_wait(&thread_buff_mutex);

    int index = sync_dequeue(free_threads);                      // retira da fila de livres
    thread_buff[index]->buffer = newport_kernel(0, bufflength);       // aloca um buffer de msgs (porta aleatoria)
    thread_buff[index]->thread = malloc(sizeof(pthread_t));
    thread_buff[index]->state = THREAD_NEW;                 // altera o estado para new

    sem_init(&(thread_buff[index]->suspend_mutex), 0, 1);

    sem_post(&thread_buff_mutex);
    return index;
}

/**
 * Desaloca a thread cujo id foi passado
 * @param id id da thread
 */
void freethread(u_short i) {
    if(i > thread_buff_length )
        return;
    
    switch(thread_buff[i]->state) {
        case THREAD_FREE:
            break;
        case THREAD_NEW:
            freeport(thread_getport(i));  // limpa o buffer de msgs
            thread_buff[i]->state = THREAD_FREE;    // altera o estado pra free
            sync_enqueue(free_threads, i);               // adiciona na fila de livres
            break;
        case THREAD_READY:
            thread_buff[i]->params = NULL;
            thread_buff[i]->function = NULL;
            freeport(thread_getport(i));  // limpa o buffer de msgs
            thread_buff[i]->state = THREAD_FREE;    // altera o estado pra free
            sync_enqueue(free_threads, i);               // adiciona na fila de livres
            break;
        case THREAD_RUNNING:
            pthread_cancel(*(thread_buff[i]->thread)); // para a execucao da thread
            thread_buff[i]->params = NULL;          
            thread_buff[i]->function = NULL;
            freeport(thread_getport(i));  // limpa o buffer de msgs
            thread_buff[i]->state = THREAD_FREE;    // altera o estado pra free
            sync_enqueue(free_threads, i);               // adiciona na fila de livres
            break;
        default:
            break;
    }
}

/**
 * Atribui a funcao que serah executada pela thread
 * @param id id da thread
 * @param func ponteiro para a funcao
 * @param params ponteiro para os parametros
 */
int thread_setfunction(u_short i, void *func, void *params) {
    if(i > thread_buff_length)
        return THERR_BUFFBOUND;
    if(thread_buff[i]->state != THREAD_NEW)
        return THERR_NOTNEW;
    
    thread_param *p = thread_createparam(i, params);
    thread_buff[i]->function    = func;
    thread_buff[i]->params      = (void*)p;
    thread_buff[i]->state       = THREAD_READY;

    return 0;
}

/**
 * Inicia a execucao da thread
 * @param id id da thread
 */
int thread_start(u_short i) {
    if(i > thread_buff_length)
        return THERR_BUFFBOUND;
    
    if(thread_buff[i]->state != THREAD_READY)
        return THERR_NOTPREPARED;
    
    thread_buff[i]->state = THREAD_RUNNING;
    int r;
    r = pthread_create(thread_buff[i]->thread, NULL, thread_buff[i]->function, thread_buff[i]->params);
    
    if(r != 0) // erro
        thread_buff[i]->state = THREAD_READY;
    
    return r; // em caso de erro retorna o erro da pthread
}

void thread_suspend(u_short id) {
    if(id > thread_buff_length)
        return ;
    
    switch(thread_buff[id]->state) {
        case THREAD_RUNNING:
            thread_buff[id]->state = THREAD_SUSPENDED;
            break;
        default:
            break;
    }
}

void thread_resume(u_short id) {
    if(id > thread_buff_length)
        return ;
    
    switch(thread_buff[id]->state) {
        case THREAD_SUSPENDED:
            thread_buff[id]->state = THREAD_RUNNING;
            break;
        default:
            break;
    }
}

void thread_wait(u_short id) {
    if(id > thread_buff_length)
        return ;
    while(thread_buff[id]->state == THREAD_SUSPENDED);
}

/**
 * Envia uma mensagem para a thread cujo id foi passado
 * @param id id da thread para a qual se deseja enviar a msg
 * @param msg mensagem a ser enviada
 */
int thread_sendmsg(u_short id_dest, int msg) {
    if(id_dest > thread_buff_length)
        return THERR_BUFFBOUND;

    switch(thread_buff[id_dest]->state) {
        case THREAD_RUNNING:
            break; // OK
        default:
            return THERR_DESTNOTSTARTED;
    }
    
    return port_sendmsg(thread_getport(id_dest), msg);
}
// caso precise ser usado no processo principal
void _thread_sendmsg(u_short id_dest, int msg) {
    if(id_dest > thread_buff_length)
        return ;
    _port_sendmsg(thread_getport(id_dest), msg);
}

/**
 * espera por uma mensagem
 * @return mensagem recebida
 */
int thread_recvmsg(u_short i) {
    if(i > thread_buff_length)
        return THERR_BUFFBOUND;
    int msg;
    if(thread_buff[i]->state != THREAD_RUNNING)
        return THERR_NOTSTARTED;
    
    msg = port_recvmsg(thread_getport(i));
    
    return msg;
}

/**
 * conta quantas mensagens existem na fila da thread i
 * @param i id da thread
 * @return num de msgs na fila da thread i
 */
u_short thread_countmsg(u_short i) {
    if(i > thread_buff_length)
        return THERR_BUFFBOUND;
    switch(thread_buff[i]->state) {
        case THREAD_RUNNING:
            return sync_queue_length(port_buff[thread_getport(i)]->msg_buffer);
        default:
            return port_buff[thread_getport(i)]->buff_max_length; // tamanho maximo
    }
}

u_short thread_getport(u_short i) {
    if(i > thread_buff_length)
        return THERR_BUFFBOUND;
    return thread_buff[i]->buffer;
}

// -------------------------------------------
// buffer de threads:
// -------------------------------------------

void init_thread_buff(int length)
{
    thread_buff_length = length;
    thread_buff = (thread**) malloc( thread_buff_length * sizeof(thread*) + 1);
    free_threads = sync_newqueue(thread_buff_length);
    sem_init(&thread_buff_mutex , 0, 1);

    int i;
    for (i = 1; i <= thread_buff_length; ++i) {
        thread_buff[i] = (thread*) malloc(sizeof(thread));
        thread* th = thread_buff[i];
        th->function = NULL;
        th->params = NULL;
        th->thread = NULL;
        th->state = THREAD_FREE;
        sync_enqueue(free_threads, i);
    }
}

// TODO: testar esta funcao
void destroy_thread_buff() {
    int i;
    for (i = thread_buff_length; i > 0; --i) {
        freethread(i);
        free(thread_buff[i]);
    }
    sync_freequeue(free_threads);
    sem_destroy(&thread_buff_mutex);
    free(thread_buff);
}

//
// Testing
//
const char* str_thread_state(thread_state state) {
    switch(state) {
        case THREAD_FREE:
            return "free";
        case THREAD_NEW:
            return "new";
        case THREAD_READY:
            return "prepared";
        case THREAD_RUNNING:
            return "started";
        default:
            return "uknown";
    }
}
const char* str_thread_error(int error) {
    switch(error) {
//        case THERR_NOTRECVD:
//            return "Message not received.";
        case THERR_NOTNEW:
            return "Thread is not in the new state.";
        case THERR_NOTPREPARED:
            return "Thread not prepared.";
        case THERR_NOTSTARTED:
            return "Thread not started.";
        case THERR_DESTNOTSTARTED:
            return "Destination thread not started.";
        case THERR_BUFFBOUND:
            return "Out of buffer's bound.";
        case PERR_NOTOCCUPIED:
            return "Destination port not occupied.";
        default:
            return "uknown";
    }
}
void print_thread(int i) {
    // mesmo com esses ifs e elseifs
    // dah falha de segmentacao quando chamado
    // depois do destroy_thread_buff
    
    if(thread_buff == NULL)
        printf("buffer is null");
    else if(thread_buff[i] == NULL)
        printf("thread %d is null", i);
    else {
        printf("Thread: %d\t", i);
        printf("State: %s\t", str_thread_state(thread_buff[i]->state) );
        printf("PortStatus: ");
        print_port(thread_getport(i));
    }
}

void print_thread_buff() {
    int i;
    printf("----- global buffer of threads -----\n");
    if(thread_buff == NULL)
        printf("NULL\n");
    else
        for(i = 0; i < thread_buff_length; ++i)
            print_thread(i);
    printf("------------------------------------\n");
}
